/********************************************************************** 
 * FILE		: XmlUtilTools.java					
 *			
 * AUTHOR   : kloyf		
 * 
 * DATE		: 2011-8-14
 * 									
 * REASON	: CREATE  
 *            							
 * VERSION  ：0.0.1
 *
 ***********************************************************************/
package com.common.util.xml;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import com.common.util.string.StringUtilTools;

/**
 * XML工具类</br> 本工具类获取xml节点使用xpath进行获取。xpath使用方法如下：</br>
 * <table>
 * <tr>
 * <td>表达式
 * <td>
 * <td>描述
 * <td>
 * </tr>
 * <tr>
 * <td>nodename
 * <td>
 * <td>选取当前节点的所有子节点
 * <td>
 * </tr>
 * <tr>
 * <td>/
 * <td>
 * <td>从根节点选取
 * <td>
 * </tr>
 * <tr>
 * <td>//
 * <td>
 * <td>从匹配选择的当前节点选择文档中的节点，而不考虑它们的位置
 * <td>
 * </tr>
 * <tr>
 * <td>.
 * <td>
 * <td>选取当前节点
 * <td>
 * </tr>
 * <tr>
 * <td>..
 * <td>
 * <td>选取当前节点的父节点
 * <td>
 * </tr>
 * <tr>
 * <td>@
 * <td>
 * <td>选取属性
 * <td>
 * </tr>
 * </table>
 * 
 * @author kloyf
 * @version 0.0.1
 */
public class XmlUtilTools extends AbstractXmlUtil {

	/**
	 * <pre>
	 * 取得xml文件Root节点</br>
	 * 直接读取xml文件的方式做操作。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @return Element 返回一个Root节点
	 */
	public static Element getRootElement(String filePath) {
		return readXml(filePath).getRootElement();
	}

	/**
	 * <pre>
	 * 取得xml文件Root节点
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件完整结构。
	 * @return Element 返回一个Root节点
	 */
	public static Element getRootElement(Document doc) {
		return doc.getRootElement();
	}

	/**
	 * <pre>
	 * 获得根节点下的一个Element对象
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件完整结构。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @return Element 返回一个节点对象。
	 */
	public static Element getElement(Document doc, String xpath) {
		@SuppressWarnings("unchecked")
		List<Element> parameterList = doc.selectNodes(xpath);
		if (parameterList != null && parameterList.size() > 0) {
			return parameterList.get(0);
		}
		return null;
	}

	/**
	 * <pre>
	 * 获得根节点下的一个Element对象。</br>
	 * 直接读取xml文件的方式来取值。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @return Element 返回一个节点对象。
	 */
	public static Element getElement(String filePath, String xpath) {
		@SuppressWarnings("unchecked")
		List<Element> parameterList = getRootElement(filePath).selectNodes(xpath);
		if (parameterList != null && parameterList.size() > 0) {
			return parameterList.get(0);
		}
		return null;
	}

	/**
	 * <pre>
	 * 根据节点名获得一个Element对象下的Element对象。
	 * </pre>
	 * 
	 * @param el
	 *            Element节点对象。
	 * @param name
	 *            需要返回的节点名。
	 * @return List 返回的节点List
	 */
	public static Element getElement(Element el, String name) {
		Element element = el.element(name);
		return element;
	}

	/**
	 * <pre>
	 * 根据父节点获得某个子节点的文本
	 * </pre>
	 * 
	 * @param element
	 *            父节点。
	 * @param elementName
	 *            子节点名。
	 * @return String 返回一个节点对象的Text
	 */
	public static String getElementText(Element element, String elementName) {
		return element.element(elementName).getText();
	}

	/**
	 * <pre>
	 * 根据节点名获得一个Element对象List
	 * </pre>
	 * 
	 * @param el
	 *            一个节点对象。
	 * @param name
	 *            需要返回的节点名。
	 * @return List 返回的节点List
	 */
	public static List<Element> getElementList(Element el, String name) {
		@SuppressWarnings("unchecked")
		List<Element> selectNodes = el.elements(name);
		return selectNodes;
	}

	/**
	 * <pre>
	 * 根据节点名获得一个Element对象List
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件完整结构。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @return List 返回的节点List
	 */
	public static List<Element> getElementList(Document doc, String xpath) {
		@SuppressWarnings("unchecked")
		List<Element> selectNodes = doc.selectNodes(xpath);
		return selectNodes;
	}

	/**
	 * <pre>
	 * 根据节点名获得一个Element对象List</br>
	 * 直接读取xml文件的方式来取值。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @return List 返回的节点List
	 */
	public static List<Element> getElementList(String filePath, String xpath) {
		@SuppressWarnings("unchecked")
		List<Element> selectNodes = getRootElement(filePath).selectNodes(xpath);
		return selectNodes;
	}

	/**
	 * <pre>
	 * 获得一个节点的内容。</br>
	 * 直接读取xml文件的方式来取值。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @return String 返回一个节点内容。
	 */
	public static String getElementText(String filePath, String xpath) {
		return getElement(filePath, xpath).getText();
	}

	/**
	 * <pre>
	 * 获得匹配节点的内容。
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件完整结构。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @return String 返回一个节点内容。
	 */
	@SuppressWarnings("unchecked")
	public static List<String> getElementsText(Document doc, String xpath) {
		List<Element> list = doc.selectNodes(xpath);
		List<String> listString = new ArrayList<String>();
		for (Element element : list) {
			listString.add(element.getText());
		}
		return listString;
	}

	/**
	 * <pre>
	 * 给xml文件添加一个节点。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件绝对路径。
	 * @param xpath
	 *            需要添加节点的xpath。
	 * @param element
	 *            需要添加的节点。
	 */
	public static void addElement(String filePath, String xpath, Element element) {
		addNode(readXml(filePath), filePath, xpath, element);
	}
	
	/**
	 * <pre>
	 * 给xml文件添加一个节点。
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件的文档结构对象。
	 * @param filePath
	 *            xml文件绝对路径。
	 * @param xpath
	 *            需要添加节点的xpath。
	 * @param element
	 *            需要添加的节点。
	 */
	public static void addElement(Document doc, String filePath, String xpath, Element element) {
		addNode(doc, filePath, xpath, element);
	}


	/**
	 * <pre>
	 * 给xml文件添加一个节点。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件绝对路径。
	 * @param xpath
	 *            需要添加节点的xpath。
	 * @param entity
	 *            需要添加的节点。
	 */
	public static void addElement(String filePath, String xpath, ElementEntity entity) {
		Document doc = readXml(filePath);

		Element e = getElement(doc, xpath).addElement(entity.getName()).addText(entity.getText().toString());

		addNode(readXml(filePath), filePath, xpath, addAttribute(e, entity.getAttribute()));
	}
	/**
	 * <pre>
	 * 给xml文件添加一个节点。
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件的文档结构对象。
	 * @param filePath
	 *            xml文件绝对路径。
	 * @param xpath
	 *            需要添加节点的xpath。
	 * @param element
	 *            需要添加的节点。
	 */
	public static void addElement(Document doc, String filePath, String xpath, ElementEntity entity) {
		Element e = getElement(doc, xpath).addElement(entity.getName()).addText(entity.getText().toString());
		addNode(doc, filePath, xpath, addAttribute(e, entity.getAttribute()));
	}
	
	

	/**
	 * <pre>
	 * 修改一个节点的文本内容。</br>
	 * 直接读取xml文件的方式做操作。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要修改的节点xpath。
	 * @param text
	 *            修改的文本内容。
	 */
	public static void updateElementText(String filePath, String xpath, String text) {
		Document doc = readXml(filePath);
		@SuppressWarnings("unchecked")
		List<Element> list = doc.selectNodes(xpath);
		for (Element element : list) {
			element.setText(text);
		}
		createXml(filePath, doc);
	}

	/**
	 * <pre>
	 * 删除一个节点</br>
	 * 直接读取xml文件的方式做操作。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要返回的节点xpath。
	 */
	public static void removeElement(String filePath, String xpath) {
		Document doc = readXml(filePath);
		@SuppressWarnings("unchecked")
		List<Node> list = doc.selectNodes(xpath);
		for (Node node : list) {
			node.detach();
		}
		createXml(filePath, doc);
	}

	/**
	 * <pre>
	 * 获得一个节点的属性内容。</br>
	 * 直接读取xml文件的方式做操作。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @param attributeName
	 *            一个节点属性名
	 * @return String 返回一个节点内容。
	 */
	public static String getElementAttributeText(String filePath, String xpath, String attributeName) {
		return getElement(filePath, xpath).attributeValue(attributeName);
	}

	/**
	 * <pre>
	 * 获得一个节点的属性内容。
	 * </pre>
	 * 
	 * @param element
	 *            父节点。
	 * @param attributeName
	 *            一个节点属性名
	 * @return String 返回一个节点内容。
	 */
	public static String getElementAttributeText(Element element, String attributeName) {
		return element.attributeValue(attributeName);
	}

	/**
	 * <pre>
	 * 给Element对象添加一个属性。
	 * </pre>
	 * 
	 * @param element
	 *            xml文档的一个节点对象。
	 * @param attriName
	 *            节点需要添加的属性名。
	 * @param attriValue
	 *            节点需要添加的属性值。
	 * @return 返回添加属性完成后的节点。
	 */
	public static Element addAttribute(Element element, String attriName, String attriValue) {
		Map<String, String> map = new HashMap<String, String>();
		map.put(attriName, attriValue);
		return addAttribute(element, map);
	}

	/**
	 * <pre>
	 * 给Element对象添加一个属性。
	 * </pre>
	 * 
	 * @param element
	 *            xml文档的一个节点对象。
	 * @param attriMap
	 *            属性的map集。
	 * @return 返回添加属性完成后的节点
	 */
	public static Element addAttribute(Element element, Map<String, String> attriMap) {
		if (!isEmpty(attriMap) && attriMap.size() > 0) {
			Set<String> keySet = attriMap.keySet();

			for (String string : keySet) {
				element.addAttribute(string, attriMap.get(string));
			}
		}
		return element;
	}

	/**
	 * <pre>
	 * 修改节点的属性文本内容。</br>
	 * 当节点里没有需要修改的属性时，给这个节点进行添加属性。否则修改节点的属性。</br>
	 * 直接读取xml文件的方式做操作。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要修改的节点xpath。
	 * @param attributeName
	 *            一个节点属性名
	 * @param text
	 *            修改的文本内容。
	 */
	public static void updateAttribute(String filePath, String xpath, String attributeName, String text) {
		Document doc = readXml(filePath);
		@SuppressWarnings("unchecked")
		List<Element> list = doc.selectNodes(xpath);
		for (Element element : list) {
			element.attribute(attributeName).setText(text);
		}
		createXml(filePath, doc);
	}

	/**
	 * <pre>
	 * 删除一个节点的属性</br>
	 * 直接读取xml文件的方式做操作。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件完整路径。
	 * @param xpath
	 *            需要返回的节点xpath。
	 * @param attributeName
	 *            一个节点属性名
	 */
	public static void removeAttribute(String filePath, String xpath, String attributeName) {
		Document doc = readXml(filePath);
		@SuppressWarnings("unchecked")
		List<Element> list = doc.selectNodes(xpath);
		for (Element element : list) {
			element.remove(element.attribute(attributeName));
		}
		createXml(filePath, doc);
	}

	/**
	 * <pre>
	 * xml文件内容转换成String类型。</br>
	 * 直接读取xml文件的方式做操作。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件的绝对路径。
	 * @return String xml字符串
	 */
	public static String xmlToString(String filePath) {
		return readXml(filePath).asXML();
	}

	/**
	 * <pre>
	 * xml文件内容转换成String类型。
	 * </pre>
	 * 
	 * @param doc
	 *            Document类型对象。
	 * @return String xml字符串
	 */
	public static String xmlToString(Document doc) {
		return doc.asXML();
	}

	/**
	 * <pre>
	 * 根据Map<String,Object>对象创建一个xml文件</br>
	 * 此map对象里可以继续存放map对象，实现xml文件里的嵌套。</br>
	 * 如：
	 * <code>
	 * Map<String,Map<String,Map<String,Object>>>。
	 * </code>
	 * 此方法只能实现简单的xml标签嵌套，如果xml标签里有属性，此方法不能正确生成xml。
	 * </pre>
	 * 
	 * @param outFilePath
	 *            文件输出完整路径
	 * @param map
	 *            需要输出成xml的map类型数据。
	 */
	public static void createXml(String outFilePath, Map<String, Object> map) {
		createXml(outFilePath, "", map);
	}

	/**
	 * <pre>
	 * 根据List对象创建一个xml文件</br>
	 * 此List对象里存放ElementEntity对象。ElementEntity对象说明请参看ElementEntity类</br>
	 * 此方法能实现各种xml格式，包括xml标签里的属性。
	 * </pre>
	 * 
	 * @param outFilePath
	 *            文件输出完整路径
	 * @param list
	 *            创建xml文件的数据
	 */
	public static void createXml(String outFilePath, List<ElementEntity> list) {
		createXml(outFilePath, "", list);
	}

	/**
	 * <pre>
	 * 根据List对象创建一个xml文件</br>
	 * 此List对象里存放ElementEntity对象。ElementEntity对象说明请参看ElementEntity类</br>
	 * 此方法能实现各种xml格式，包括xml标签里的属性。
	 * </pre>
	 * 
	 * @param outFilePath
	 *            文件输出完整路径
	 * @param rootName
	 *            根节点名
	 * @param list
	 *            创建xml文件的数据
	 */
	public static void createXml(String outFilePath, String rootName, List<ElementEntity> list) {
		Document doc = DocumentHelper.createDocument();
		if (null == rootName || "".equals(rootName)) {
			rootName = DEFULT_ROOT_NAME;
		}
		// 创建根节点
		Element rootElement = doc.addElement(rootName);
		for (ElementEntity elementEntity : list) {
			if (!isTextType(elementEntity.getText())) {
				createXml(rootElement, rootName, elementEntity);
			} else {
				createXml(rootElement.addElement(elementEntity.getName()), elementEntity.getName(), elementEntity);
			}
		}
		createXml(outFilePath, doc);
	}

	/**
	 * <pre>
	 * 根据Map<String,Object>对象创建一个xml文件</br>
	 * 此map对象里可以继续存放map对象，实现xml文件里的嵌套。</br>
	 * 如：
	 * <code>
	 * Map<String,Map<String,Map<String,Object>>>。
	 * </code>
	 * 此方法只能实现简单的xml标签嵌套，如果xml标签里有属性，此方法不能正确生成xml。
	 * </pre>
	 * 
	 * @param outFilePath
	 *            文件输出完整路径
	 * @param rootName
	 *            根节点名
	 * @param map
	 *            需要输出成xml的map类型数据。
	 */
	public static void createXml(String outFilePath, String rootName, Map<String, Object> map) {
		Document doc = DocumentHelper.createDocument();
		if (null == rootName || "".equals(rootName)) {
			rootName = DEFULT_ROOT_NAME;
		}
		// 创建根节点
		Element rootElement = doc.addElement(rootName);
		createXml(rootElement, rootName, map);

		createXml(outFilePath, doc);
	}

	/**
	 * <pre>
	 * 根据xml字符串创建一个xml文件
	 * </pre>
	 * 
	 * @param outFilePath
	 *            文件输出完整路径
	 * @param xmlString
	 *            xml字符串
	 */
	public static void createXml(String outFilePath, String xmlString) {
		Document doc = null;
		try {
			doc = DocumentHelper.parseText(xmlString);
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		createXml(outFilePath, doc);
	}

	/**
	 * <pre>
	 * 私有方法。递归一个ElementEntity对象。</br>
	 * 如果ElementEntity对象里继续存放ElementEntity对象或者List，则将这个ElementEntity对象进行递归，以此解决xml嵌套问题。
	 * </pre>
	 * 
	 * @param element
	 *            在此节点下进行创建新的节点。此节点相当于根节点。
	 * @param rootName
	 *            节点名。
	 * @param ElementEntity
	 *            需要创建。
	 * @return Element 返回创建好的节点。
	 */
	@SuppressWarnings("unchecked")
	private static Element createXml(Element element, String rootName, ElementEntity elementEntity) {

		if (elementEntity.getText() instanceof ElementEntity) {
			/**
			 * 当ElementEntity中，Text类型为ElementEntity时，直接进行递归。
			 */
			createXml(element, elementEntity.getName(), (ElementEntity) elementEntity.getText());
		} else if (elementEntity.getText() instanceof List) {
			/**
			 * 当ElementEntity中，Text类型为List时，对其进行遍历后，再进行递归。
			 */
			for (ElementEntity elementEntity2 : (List<ElementEntity>) elementEntity.getText()) {
				createXml(element, elementEntity2.getName(), elementEntity2);
			}
		} else {
			Element element2 = element.addElement(elementEntity.getName());

			element2.addText(!StringUtilTools.isEmpty(elementEntity.getText()) ? elementEntity.getText().toString() : "");
			// 给一个节点设置属性值。
			addAttribute(element2, elementEntity.getAttribute());
		}
		return element;
	}

	/**
	 * <pre>
	 * 私有方法。给xml文件添加一个节点。
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件的文档结构对象。
	 * @param filePath
	 *            xml文件绝对路径。
	 * @param xpath
	 *            需要添加节点的xpath。
	 * @param element
	 *            需要添加的节点。
	 */
	private static void addNode(Document doc, String filePath, String xpath, Element element) {
		Element e = getElement(doc, xpath);
		e.add(element);
		createXml(filePath, doc);
	}

	/**
	 * <pre>
	 * 私有方法。给xml文件添加一个节点,并返回这个xml文件的文档结构。
	 * </pre>
	 * 
	 * @param filePath
	 *            xml文件绝对路径。
	 * @param xpath
	 *            需要添加节点的xpath。
	 * @param element
	 *            需要添加的节点。
	 * @return 返回xml文件的文档结构对象
	 */
	@SuppressWarnings("unused")
	private static Document addNodeAndReturnDoc(String filePath, String xpath, Element element) {
		Document doc = readXml(filePath);
		return addNodeAndReturnDoc(doc, xpath, element);
	}

	/**
	 * <pre>
	 * 私有方法。给xml文件添加一个节点,并返回这个xml文件的文档结构。
	 * </pre>
	 * 
	 * @param doc
	 *            xml文件绝对路径。
	 * @param xpath
	 *            需要添加节点的xpath。
	 * @param element
	 *            需要添加的节点。
	 * @return 返回xml文件的文档结构对象
	 */
	private static Document addNodeAndReturnDoc(Document doc, String xpath, Element element) {
		Element e = getElement(doc, xpath);
		e.add(element);
		return doc;
	}

	/**
	 * <pre>
	 * 私有方法。</br> 
	 * 判断一个ElementEntiey对象中的Text属性是否为ElementEntiey类型或者List类型。</br>
	 * </pre>
	 * 
	 * @param object
	 *            传入为ElementEntiey对象中Text属性。
	 * @return booblean 当Text属性中的值为ElementEntiey或者List类型时，返回true，否则返回false.
	 */
	private static boolean isTextType(Object object) {
		boolean flag = false;
		if (object instanceof ElementEntity || object instanceof List) {
			flag = true;
		}
		return flag;
	}

	/**
	 * <pre>
	 * 私有方法，递归一个map对象。</br>
	 * 如果map对象里继续存放map，则将这个map对象进行递归，以此解决xml嵌套问题。
	 * </pre>
	 * 
	 * @param element
	 *            在此节点下进行创建新的节点。此节点相当于根节点。
	 * @param rootName
	 *            节点名。
	 * @param map
	 *            需要创建xml文件的数据。
	 * @return Element 返回创建好的节点。
	 */
	@SuppressWarnings("unchecked")
	private static Element createXml(Element element, String rootName, Map<String, Object> map) {
		/**
		 * 根据根节点创建一个子节点
		 */
		Element el = element.addElement(rootName);

		Set<String> keySet = map.keySet();
		for (String key : keySet) {
			if (map.get(key) instanceof Map) {
				// 如果map里是map对象，则进行递归
				createXml(el, key, (Map<String, Object>) map.get(key));
			} else {
				/**
				 * 以<节点名>节点内容</节点名>形式创建xml里的节点。
				 */
				el.addElement(key).addText(map.get(key).toString());
			}
		}
		return element;
	}

	/**
	 * <pre>
	 * 根据输入对象，创建一个xml文件。</br>
	 * 此方法为私有，公共创建xml文件方法。
	 * </pre>
	 * 
	 * @param outFilePath
	 *            xml文件完整保存路径
	 * @param obj
	 *            xml文件内容
	 */
	private static void createXml(String outFilePath, Object doc) {
		XMLWriter writer = null;
		try {
			OutputFormat outputFormat = OutputFormat.createPrettyPrint();
			writer = new XMLWriter(new FileWriter(outFilePath), outputFormat);
			writer.write(doc);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}